import HRTC from './sdk/hrtc'
import {HmacSHA256} from "crypto-js";
import TouchController from "./TouchController";
import MessageHandler from "./MessageHandler";
import Util from "../Util";
import {rtc_webjs_grs_sdk_global_route_config} from "./grs";
import PROTOCOL_CONFIG from "../config/protocolConfig";
import Subscribe from "../common/Subscribe";
import {FRAME_TYPE_MAP} from "../config/commonConfig";
import AESGCMCrypto from "../AESGCMCrypto";
import AutoRotation from "../AutoRotation";
import DirectionHandler from "../DirectionHandler";
import TouchHandler from "../TouchHandler";
import KeyboardInput from "../KeyboardInput";
import FrameParser from "../FrameParser";
import Logger from "../Logger";

const PLAYER = 'player';
const DEFAULT_VOLUME_VALUE = 50;
const DEFAULT_ORIENTATION = 'PORTRAIT';

class RTCChannel {
    constructor(options) {
        this.util = new Util();
        this.subscribe = new Subscribe([
            'netStateChange',
            'appStateChange',
            'audioStateChange',
            'cloudAppData'
        ]);
        this.fullscreenStatus = false;
        this.options = {
            appId: String(options.appId),
            appKey: String(options.appKey),
            env: String(options.env),
            roomId: String(options.roomId),
            phoneIp: String(options.ip),
            phonePort: String(options.port),
            sessionId: String(options.session_id),
            backgroundTimeout: String(options.background_timeout),
            availablePlayTime: String(options.available_playtime),
            ticket: String(options.ticket),
            aesKey: String(options.aes_key),
            authTimeStamp: String(options.auth_ts),
            containerId: options.containerId,
            userId: String(options.user_id || ''),
            isMobile: /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent),
            touchTimeout: String(Math.max(Number(options.touch_timeout || 0), 0)),
            sdkVersion: __APP_VERSION__,
        }
        this.options.autoRotate = options.auto_rotate && this.options.isMobile;
        if(this.options.autoRotate){
            this.autoRotation = null;
        }

        if (options.media_config) {
            this.options.mediaConfig = options.media_config;
        }
        this.options.volume = DEFAULT_VOLUME_VALUE;
        this.sessionId = this.options.sessionId || this._generateGUID();

        this._configEnv(this.options.env);

        this.options.role = 'anchor';
        this.options.userId = this._generateGUID();

        this.resolution = {
            x: 720,
            y: 1280
        }

        this.orientation = PROTOCOL_CONFIG.ORIENTATION[0];

        this.curResolution = {...PROTOCOL_CONFIG.DEFAULT_RESOLUTION};
        this.nextResolution = {...PROTOCOL_CONFIG.DEFAULT_RESOLUTION};
        this.frameParser = new FrameParser(this.options.supportAudio);
    }

    updateResolutionAndTouch() {
        if (this.player) {
            // resize之前更新canvas大小
            if (!this.options.isMobile) {
                this.directionHandler.updateCanvasSize(this.orientation, this.isMSE);
            }
            this.touchHandler.updateOrientation(this.orientation);
            this.autoRotation && this.autoRotation.updateOrientation(this.orientation);
            this.directionHandler.update(this.orientation, this.touchHandler.displayBox);
        }
    }

    init() {
        let crypt = new AESGCMCrypto();
        let iv = crypt.iv();
        let verifiedData = {
            ip: this.options.phoneIp,
            port: this.options.phonePort,
            session_id: this.sessionId,
            backgroundTimeout: this.options.backgroundTimeout,
            available_playtime: this.options.availablePlayTime,
            user_id: this.options.userId
        }

        let encryptedData = {...verifiedData, touch_timeout: this.options.touchTimeout};
        Promise.all([
            crypt.encrypt(JSON.stringify(encryptedData), this.options.aesKey, iv),
            crypt.abstract(Object.values(verifiedData).join(''))
        ]).then(([cipherText, verifyText]) => {
            this.startParams = {
                user_id: this.options.user_id,
                ticket: this.options.ticket,
                session_id: this.options.session_id,
                auth_ts: this.options.auth_ts,
                verify_data: verifyText,
                encrypted_data: cipherText,
                aes_iv: iv,
                sdk_version: this.options.sdkVersion,
                protocol_version: 'v2',
                client_type: '3',
                media_config: {
                    ...this.options.mediaConfig,
                    frame_type: FRAME_TYPE_MAP.TYPE264
                }
            };
            this._connect();

            this.keyboardInput = new KeyboardInput(this.options.containerId, this.sendMessage);
            this.keyboardInput.init();
            this.touchHandler = new TouchHandler({
                player: this.player,
                isMobile: this.options.isMobile,
                sendHandler: this.sendMessage2,
                isDebug: this.options.isDebug,
                autoRotate: this.options.autoRotate,
                inputId: this.keyboardInput && this.keyboardInput.inputId || ''
            });
            this.touchHandler.start();
            if(this.touchHandler.displayBox.width && this.touchHandler.displayBox.height) {
                const ctrlEle = document.getElementById('controlBtn');
                const containerEle = document.getElementById('container');
                const left = (containerEle.offsetWidth - this.touchHandler.displayBox.width)/2 + 20;
                const top = (containerEle.offsetHeight - this.touchHandler.displayBox.height)/2 + 125;
                if (this.options.isMobile) {
                    ctrlEle.style.top = `20%`;
                    ctrlEle.style.left = `5%`;
                } else {
                    ctrlEle.style.top = `${top}px`;
                    ctrlEle.style.left = `${left}px`;
                }
            }

            if(this.options.autoRotate){
                this.autoRotation = new AutoRotation(this.options.containerId,
                    DEFAULT_ORIENTATION, this.options.isMobile,rotateDegrees => {
                        // 旋转后更新触控，并根据旋转角度判断使用云机键盘还是真机键盘
                        this.touchHandler.resize();
                    });
                this.autoRotation.init();
            }

            this.directionHandler = new DirectionHandler({
                containerId: this.options.containerId,
                isMobile: this.options.isMobile,
                playerContainerId: this.playerContainer
            });

            this.listenPlayerSizeChange();
            this.updateResolution();
        });
    }

    updateResolution() {
        if (this.curResolution.width === this.nextResolution.width && this.curResolution.height === this.nextResolution.height) {
            return;
        }

        this.touchHandler.updateResolution(this.nextResolution.width, this.nextResolution.height);
        this.curResolution = this.nextResolution;
    }

    listenPlayerSizeChange() {
        if (this.isMSE) {
            // 视频元信息加载完成后，主动触发 resize 事件更新相关坐标位置
            this.loadedmetadataCallback = () => {
                this.touchHandler.resize();
                // 使用完毕移除事件监听
                this.player.removeEventListener('loadedmetadata', this.loadedmetadataCallback);
                this.loadedmetadataCallback = null;
            };

            this.player.addEventListener('loadedmetadata', this.loadedmetadataCallback);

            // video 大小变化时需要持续更新坐标位置
            this.videoResizeCallback = () => {
                this.touchHandler.resize();
            };

            this.player.addEventListener('resize', this.videoResizeCallback);
        } else {
            // 监听 canvas 大小变化
            const MutationObserver = window.MutationObserver;
            this.canvasObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.attributeName === 'width' || mutation.attributeName === 'height') {
                        this.touchHandler.resize();
                    }
                });
            });
            this.canvasObserver.observe(this.player, {attributes: true});
        }
    }

    async _connect() {
        this._initPlayer();
        this._initHandler();
        this._createEngine();
        this._initClientEvent();
        this._joinRoomPre()
        await this._joinRoom();
        await this._joinRoomPost();
    }

    destroy() {
        this.messageHandler.sendStopCmdControl();
        setTimeout(() => {
            this._stopHeartBeat();
            this.touchController.destroy();
            this.stream.unsubscribe();
            this.client.leave().then(r => console.log("leave success!"));
        }, 1000);
    }

    getVolume() {
        return this.options.volume;
    }

    setVolume(value) {
        this.options.volume = value;
    }

    pause() {
        this.messageHandler.sendPauseCmdControl();
    }

    resume() {
        this.messageHandler.sendResumeCmdControl();
    }

    reconnect() {
        this.destroy();
        this.init();
    }

    fullscreenToggle(fullscreenElementId) {
        if (this.fullscreenStatus) {
            this._exitFullscreen();
        } else {
            this._enterFullscreen(fullscreenElementId);
        }
    }

    isFullscreen() {
        return this.fullscreenStatus;
    }

    on(eventName, callback) {
        this.subscribe.on(eventName, callback);
    }

    off(eventName, callback) {
        this.subscribe.off(eventName, callback);
    }

    setResolution(clarityVal) {
        const frameType = 'H264';

        const allMediaConfig = PROTOCOL_CONFIG[frameType];
        const config = allMediaConfig[`${mediaWidth}P`][clarityVal];
        this.setMediaConfig(config);
    }

    setMediaConfig(config) {
        this.messageHandler.sendMediaConfig(config);
    }

    sendMessage(message) {
        return this.client.sendCommandMsg(message);
    }

    sendMessage2(message) {}

    _generateGUID() {
        return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => {
            const r = AESGCMCrypto.getRandomValue() * 16 | 0;
            const v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    _initPlayer() {
        this.player = document.createElement('div');
        this.player.id = PLAYER;
        this.player.style.margin = '0 auto';

        this.rtcStatsInfo = document.createElement('p');
        this.rtcStatsInfo.style.color = 'white';
        this.rtcStatsInfo.style.backgroundColor = 'green';
        this.rtcStatsInfo.style.fontSize = '6px';

        this.fullscreenBtn = document.createElement('button');
        this.fullscreenBtn.innerHTML = "全屏";
        this.fullscreenBtn.addEventListener('click', () => {
            this.fullscreenToggle();
        });

        this.playBtn = document.createElement('button');
        this.playBtn.innerHTML = "播放";
        this.playBtn.disabled = true;
        this.playBtn.addEventListener('click', () => {
            this.stream.resume();
        });

        this.rtcContainer = document.createElement('div');
        this.rtcContainer.style.position = 'fixed';
        this.rtcContainer.style.left = '0px';
        this.rtcContainer.style.top = '0px';

        this.rtcContainer.appendChild(this.rtcStatsInfo);
        this.rtcContainer.appendChild(this.fullscreenBtn);
        this.rtcContainer.appendChild(this.playBtn);

        this.playerContainer = document.createElement('div');
        this.playerContainer.style.margin = '0 auto';
        this.playerContainer.appendChild(this.player);
        this.playerContainer.appendChild(this.rtcContainer);

        const container = document.getElementById(this.options.containerId);
        container.appendChild(this.playerContainer);

        document.addEventListener('webkitfullscreenchange', this._fullscreenStateRefresh.bind(this));
        document.addEventListener('mozfullscreenchange', this._fullscreenStateRefresh.bind(this));
        document.addEventListener('fullscreenchange', this._fullscreenStateRefresh.bind(this));
        document.addEventListener('msFullscreenChange', this._fullscreenStateRefresh.bind(this));
    }

    _initHandler() {
        this.messageHandler = new MessageHandler(this.sendMessage.bind(this), this.options);
        this.touchController = new TouchController({
            resolution: this.resolution,
            player: this.player,
            messageHandler: this.messageHandler
        });
        this.touchController.init();
    }

    _createEngine() {
        this.client = HRTC.createClient({
            appId: this.options.appId,
            countryCode: 'CN',
        });
    }

    _initClientEvent() {
        this.client.off('Error');
        this.client.off('peer-join');
        this.client.off('peer-leave');
        this.client.off('stream-added');
        this.client.off('stream-updated');
        this.client.off('stream-removed');
        this.client.off('stream-subscribed');
        this.client.off('rtc-stats');
        this.client.off('network-quality');
        this.client.off('cmd-msg-received');

        this.client.on('Error', (errorInfo) => {
            console.log(`Error:${JSON.stringify(errorInfo)}`);
        });
        this.client.on('peer-join', (event) => {
            console.log(`peer-join`);
        });
        this.client.on('peer-leave', (event) => {
            console.log(`peer-leave`);
        });
        this.client.on('stream-added', (event) => {
            console.log(`stream-add`);
            this._subscribeStream(event.stream);
        });
        this.client.on('stream-updated', (event) => {
            console.log('stream-updated');
            this._subscribeStream(event.stream);
        });
        this.client.on('stream-removed', (event) => {
            console.log('stream-removed');
            this._streamClose(event.stream);
        });
        this.client.on('stream-subscribed', (event) => {
            console.log('stream-subscribed');
            this._streamPlay(event.stream);
        });
        this.client.on('rtc-stats', (rtcStatsInfo) => {
            this._dealRtcStats(rtcStatsInfo);
        });
        this.client.on('network-quality', (networkQualityInfo) => {
            console.log(`network-quality:${JSON.stringify(networkQualityInfo)}`);
        });
        this.client.on('cmd-msg-received', (commandMsgInfo) => {
            this._dealCommandMsgInfo(commandMsgInfo);
        });
        this.client.on('cmd-channel-established', () => {
            console.log('cmd-channel-established');
            this.messageHandler.sendStartCmdControl(this.startParams);
            // this._startHeartBeat();
        });
    }

    _joinRoomPre() {
        this.client.enableCommandMsg(true);
        this.client.setNetworkBandwidth({maxBandwidth: 30 * 1024});
    }

    async _joinRoom() {
        try {
            const userInfo = this._getUserInfo();
            await this.client.join(this.options.roomId, {
                userId: userInfo.userId,
                username: userInfo.userId,
                signature: userInfo.signature,
                ctime: userInfo.ctime,
                role: userInfo.role,
            });
        } catch (e) {
            console.log(`join room failure:${e}`);
        }
    }

    async _joinRoomPost() {
        await this.client.enableRtcStats(true, 1000);
    }

    _getUserInfo() {
        const signatureCtime = 12 * 60 * (60 - 2) * 1000;
        const ctime = Math.floor((signatureCtime + new Date().getTime()) / 1000);

        let message;
        const authWithRole = this.options.authWithRole;
        if (authWithRole) {
            message = `${this.options.appId}+${this.options.roomId}+${this.options.userId}+${this.options.role}+${ctime}`;
        } else {
            message = `${this.options.appId}+${this.options.roomId}+${this.options.userId}+${ctime}`;
        }
        const signature = HmacSHA256(message, this.options.appKey).toString();
        return {
            userId: this.options.userId,
            signature: signature,
            ctime: ctime,
            role: 0,
        }
    }

    async _subscribeStream(stream) {
        return this.client.subscribe(stream, {
            video: true,
            audio: true,
        });
    }

    async _streamPlay(stream) {
        this.playBtn.disabled = false;
        this.stream = stream;
        return stream.play(PLAYER, {
            objectFit: 'contain',
            muted: false
        });
    }

    _streamClose(stream) {
        stream.close();
        this.stream = null;
        this.playBtn.disabled = true;
    }

    _startHeartBeat() {
        this.heartBeatTimer = setTimeout(() => {
            this._stopHeartBeat();
            this.messageHandler.sendHeartBeat();
            this._startHeartBeat();
        }, 100);
    }

    _stopHeartBeat() {
        if(this.heartBeatTimer){
            clearTimeout(this.heartBeatTimer);
        }
    }

    _configEnv(env) {
        switch (env) {
            case 'root':
                this._configRoot();
                break;
            case 'gammagreen':
                this._configGammagreen();
                break;
            case 'gammablue':
                this._configGammablue();
                break;
        }
    }

    _configRoot() {
        HRTC.setParameter('FRAME_ENCRYPTION_MODE', 3);
        this.options.authWithRole = false;
    }

    _configGammagreen() {
        HRTC.setParameter('ENVIRONMENT_SUFFIX', {grsKey: 'GREEN', pathSuffix: 'gammagreen'});
        HRTC.setParameter('GRS_LOCAL_EXT_INFO', {
            rtc_webjs_grs_sdk_global_route_config: rtc_webjs_grs_sdk_global_route_config,
        });
        this.options.authWithRole = false;
    }

    _configGammablue() {
        HRTC.setParameter('FRAME_ENCRYPTION_MODE', 3);
        HRTC.setParameter('GRS_LOCAL_EXT_INFO', {
            rtc_webjs_grs_sdk_global_route_config: rtc_webjs_grs_sdk_global_route_config,
        });
        this.options.authWithRole = true;
    }

    _enterFullscreen(fullscreenElementId) {
        this.fullscreenEle = document.getElementById(fullscreenElementId) || document.documentElement;

        const requestMethod = this.fullscreenEle.requestFullScreen // W3C
            || this.fullscreenEle.mozRequestFullScreen // Chrome 等
            || this.fullscreenEle.webkitRequestFullScreen // FireFox
            || this.fullscreenEle.msRequestFullScreen; // IE11
        if (requestMethod) {
            requestMethod.call(this.fullscreenEle);
        }
        this.fullscreenStatus = true;
    }

    _exitFullscreen() {
        const exitMethod = document.exitFullscreen // W3C
            || document.mozCancelFullScreen // FireFox
            || document.webkitExitFullscreen // Chrome 等
            || document.msExitFullscreen; // IE11
        if (exitMethod) {
            exitMethod.call(document);
        }
        this.fullscreenStatus = false;
    }

    _dealRtcStats(rtcStatsInfo) {
        function getStats(info, stats) {
            return info[stats] !== undefined ? stats + ':' + info[stats] + ' |' : '';
        }

        let infoStr = '';
        for (let i = 0, len = rtcStatsInfo.length; i < len; i++) {
            let info = rtcStatsInfo[i];
            infoStr += `
            ${info.mediaType}:
            ${getStats(info, 'bitrate')}
            ${getStats(info, 'frameRate')}
            ${getStats(info, 'rtt')}
            ${getStats(info, 'jitter')}
            ${getStats(info, 'pktLossRate')}
            <br>
            `
        }
        this.rtcStatsInfo.innerHTML = infoStr;
    }

    _dealCommandMsgInfo(commandMsgInfo) {
        console.log(`_dealCommandMsgInfo:${commandMsgInfo.msg}`);
        this.messageHandler.dealReceiveMsg(
            commandMsgInfo.msg,
            {
                cmdControlCallback: this._dealCmdControlCallback,
                heartBeatCallback: this._dealHeartBeatCallback,
                orientationCallback: this._dealOrientationCallback,
                keyboardInputCallback: this._dealKeyboardInputCallback,
                cameraCallback: this._dealCameraCallback,
                microphoneCallback: this._dealMicrophoneCallback,
                phoneControlCallback: this._dealPhoneControlCallback
            }
        )
    }

    _dealCmdControlCallback(code) {
        console.log(`cmd control callback:${code}`);
        const codeConfig = PROTOCOL_CONFIG.CMD_RESP_TYPE;
        switch (code) {
            case codeConfig.CONNECT_FAILED:
            case codeConfig.VERIFY_FAILED:
            case codeConfig.START_FAILED:
                Logger.debug('Receive failed response, destroy');
                this.destroy();
                break;
            case codeConfig.PLAY_TIMEOUT:
                Logger.debug('Play timeout, destroy');
                this.destroy();
                break;
            case codeConfig.TOUCH_TIMEOUT:
                Logger.debug('Touch timeout, destroy');
                this.destroy();
                break;
            case codeConfig.PAUSE_TIMEOUT:
                Logger.debug('Pause timeout, destroy');
                this.destroy();
                break;
            case codeConfig.MEDIA_CONFIG_SUCCESS:
                Logger.debug('Setting media config success');
                this.updateResolution();
                break;
            case codeConfig.START_SUCCESS:
                Logger.debug('START_SUCCESS');
                break;
            case codeConfig.INVALID_OPERATION:
            case codeConfig.RECONNECT_PARAMETER_INVALID:
            case codeConfig.RECONNECT_SERVER_UNREACHABLE:
            case codeConfig.RECONNECT_ENGING_START_ERROR:
                Logger.debug('RECONNECT_FAILED');
                break;
            case codeConfig.H265_NOT_SUPPORT:
                Logger.debug('H265_NOT_SUPPORT');
                break;
            default:
                break;
        }
    }

    _dealHeartBeatCallback(msg) {
        console.log(`heart beat callback:${msg}`);
    }

    _dealOrientationCallback(orientation) {
        console.log(`orientation callback:${orientation}`);
        this.orientation = orientation;
        this.updateResolutionAndTouch();
    }

    _dealKeyboardInputCallback(msg) {
        console.log(`keyboard input callback:${msg}`);
        if (!this.keyboardInput || msg.length < PROTOCOL_CONFIG.KEYBOARD_INPUT_HEADER_LENGTH) {
            return;
        }
        const [type, highBytesSize, lowByteSize] = msg;
        if (type === PROTOCOL_CONFIG.KEYBOARD_INPUT_MSG_TYPE.INPUT_EVENT) {
            const len = (highBytesSize << 8) + lowByteSize;
            const input = len > 0 ? this.util.decodeUTF8(msg, PROTOCOL_CONFIG.KEYBOARD_INPUT_HEADER_LENGTH,
                PROTOCOL_CONFIG.KEYBOARD_INPUT_HEADER_LENGTH + len) : '';
            this.keyboardInput.start(input);
            this.touchHandler.updateKeyBoardMode('KEYBOARD_INPUT');
        } else if (type === PROTOCOL_CONFIG.KEYBOARD_INPUT_MSG_TYPE.HIDE_KEYBOARD_EVENT) {
            this.keyboardInput.stop();
            this.touchHandler.updateKeyBoardMode('KEYBOARD_MAP');
        }
    }

    _dealCameraCallback(msg) {
        console.log(`camera callback:${msg}`);
    }

    _dealMicrophoneCallback(msg) {
        console.log(`microphone callback:${msg}`);
    }

    _dealPhoneControlCallback(msg) {
        console.log(`phone control callback:${msg}`);
        if (!msg) {
            return;
        }
        const buf = new Uint8Array(msg);
        if (!buf.length) {
            return;
        }
        const code = buf[0];
        const stateConfig = PROTOCOL_CONFIG.PHONE_CONNECTION_STATE[code];
        if (stateConfig) {
            Logger.debug(stateConfig.message);
            this.appState = stateConfig;
            this.subscribe.trigger('appStateChange', {...this.appState});
        }
    }

    _fullscreenStateRefresh() {
        this.fullscreenStatus = Boolean(document.fullscreenElement
            || document.msFullscreenElement
            || document.mozFullScreenElement
            || document.webkitFullscreenElement);
        console.log(`_fullscreenStateRefresh:${this.isFullscreen()}`);
        if (this.isFullscreen()) {
            this.fullscreenBtn.innerHTML = '退出全屏';
        } else {
            this.fullscreenBtn.innerHTML = '全屏';
        }
    }
}

export default RTCChannel;